iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 30
2
Modern Web

從比入門再往前一點開始,一直到深入React.js系列 第 30

【Day.30】React進階 - Styled-Components: React的CSS解決方案 | 系列總結

  • 分享至 

  • xImage
  •  

(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問

在過去的29天內,除了直接綁在JSX元素上外,我們從來都沒有提過要如何在React處理CSS code。其實只要設定好打包工具,我們就能直接在任何元件檔使用import引入css檔。例如如果你是使用create-react-app建立專案的朋友,就能這樣寫:

import ".css檔路徑";

然而這樣做有個問題。

一般在做SPA的時候,通常是把所有css檔打包成一個或多個檔案,並在第一次載入網頁時就全部引入。但這會讓開發者原本想要隸屬於個別元件的css程式碼同時生效,導致本來應該分開的css程式碼互相影響。 如果想要最簡易的解決這個問題,除了把style寫在JSX的props上外,就要引用第三方套件。

除非你能保證,你自己、你的同事、你未來的接班人、你過去的古人(?) 都沒有使用同樣的class、id 或是其他哩哩摳摳的選取器......

現在,我們就來介紹一款熱門的React style處理套件 - Styled-Components

安裝Styled-Components

請打開terminal,輸入:

npm install --save styled-components

Styled-Component基礎使用

Styled-Component可以讓我們撰寫css code後,產生「專屬這組css」的React元件。他的語法很特別:

import styled from 'styled-components';

const 元件 = styled.你想使用的DOM元素`css程式碼`

//在JSX使用時
<元件></元件>

css程式碼要在.js檔以字串的方式寫在最後面。舉例來說,本來我們的MenuItem長這樣:

  • src/component/MenuItem.js
import React, { memo } from 'react';

const menuItemStyle = {
    marginBottom: "7px",
    paddingLeft: "26px",
    listStyle: "none"
};

function MenuItem(props){
    return <li style={menuItemStyle}>{props.text}</li>;
}

export default memo(MenuItem);

切換成Styled-Components後就會變這樣:

import React, { memo } from 'react';
import styled from 'styled-components';

const MenuStyleItem = styled.li`
    margin-bottom: 7px;
    padding-left: 26px;
    list-style: none;
`;

function MenuItem(props){
    return  <MenuStyleItem>{props.text}</MenuStyleItem>;
}

export default memo(MenuItem);

實際觀看執行結果,你會發現顯示的雖然是一般的<li>,但它上面多了一組特別的class,而且我們撰寫的css程式碼自動以這個class為選取器運作:

因為相同的Styled-Components元件會產生同樣且不與其他元件重複的class,所以我們就能避免在不同地方使用到相同css選取器而互相影響。

另外,一般會習慣把定義Styled-Components的地方拉出來和本來的元件分開。就跟以前會把css跟html檔分開的感覺很像。只是現在你又能更方便的製造相同style的元素:

  • (新創建)src/component/MenuItemStyle.js
import styled from 'styled-components';

const MenuStyleItem = styled.li`
    margin-bottom: 7px;
    padding-left: 26px;
    list-style: none;
`;


export { MenuStyleItem };
  • src/component/MenuItem.js
import React, { memo } from 'react';
import { MenuStyleItem } from './MenuItemStyle';

function MenuItem(props){
    return  <MenuStyleItem>{props.text}</MenuStyleItem>;
}

export default memo(MenuItem);

傳遞參數給Styled-Components

你可以在Styled-Components上直接綁定本來原生DOM元素就會運作的props,例如onClick,該props會自動被給予DOM元素,不需要做任何而外的事情。

另外,我們也能透過ES6的字串模板,讓css根據props的值而變動。像是下面我們給了MenuStyleItem一個color={"blue"}:

  • src/component/MenuItem.js
import React, { memo } from 'react';
import { MenuStyleItem } from './MenuItemStyle';

function MenuItem(props){
    return  <MenuStyleItem color={"blue"}>{props.text}</MenuStyleItem>;
}

export default memo(MenuItem);

我們就能把props.color設為color的值(如果沒有給props.color則把color設定為"black")。

  • src/component/MenuItemStyle.js
import styled from 'styled-components';

const MenuStyleItem = styled.li`
    margin-bottom: 7px;
    padding-left: 26px;
    list-style: none;
    color: ${props => props.color ? props.color : "black"};
`;


export { MenuStyleItem };

以預設props設定style主題

你可以透過Styled元件.defaultProps來設定給參數預設值。藉此達到製作「主題」的效果。當使用元件的人沒有給對應的style的props時,Styled元件就會以預設的參數造型顯示:

  • src/component/MenuItemStyle.js
import styled from 'styled-components';

const MenuStyleItem = styled.li`
    margin-bottom: 7px;
    padding-left: 26px;
    list-style: none;
    color: ${props => props.theme.color};
`;

MenuStyleItem.defaultProps = {
    theme: {
        color: "mediumseagreen"
    }
}

export {MenuStyleItem};
  • src/component/MenuItem.js
import React, { memo } from 'react';
import { MenuStyleItem } from './MenuItemStyle';

function MenuItem(props){
    return  <MenuStyleItem>{props.text}</MenuStyleItem>;
}

export default memo(MenuItem);

另外你也可以透過搭配useContext或是Redux達到製造相同主題的效果,這裡就不示範了。

比較好的分檔方式

最後,比較乾淨的分檔方式應該是為單一元件創立一個資料夾,在裡面放置專屬於它的元件程式和style程式。但這個就是不同人/團隊的習慣問題了。

  • src/component/MenuItem/index.js
import React, { memo } from 'react';
import { MenuStyleItem } from './style';

function MenuItem(props){
    return  <MenuStyleItem onClick={()=>{console.log(props.handleClick)}}>{props.text}</MenuStyleItem>;
}

export default memo(MenuItem);
  • src/component/MenuItem/style.js
import styled from 'styled-components';

const MenuStyleItem = styled.li`
    margin-bottom: 7px;
    padding-left: 26px;
    list-style: none;
    color: ${props => props.theme.color};
`;

MenuStyleItem.defaultProps = {
    theme: {
        color: "mediumseagreen"
    }
}

export {MenuStyleIStyleItem};

另外,StyleComponent中也能撰寫像是偽元素的語法,更多進階使用可以參考官方文件

30天結束 | 系列總結

在這個系列中,除了這兩個hook之外

  • useImperativeHandle
  • useDebugValue

我們已經把所有其他官方提供的React hook、現今業界React專案開發一定會用到的語法、套件以及他們需要用到的對應情境都講解了一遍。上面這兩個有需要的時候再去查就好。

有關React SSR的文章我應該會後續幾個月內再繼續發,到時候也是會發在這個系列。

最後,我希望看這系列文的讀者不是只為了工作才逼自己跟隨React語法寫程式,而是能夠理解框架是長期演變而來的。所謂的框架,只是把過去開發者發現中大型專案幾乎一定會用到的Design Pattern、架構都幫你封裝好。我們應該思考的是如何讓「元件化的架構」搭配框架提供的功能而變得更乾淨、更好用、更能彈性的封裝,而不是單純寫出一個能用的React Component。

這30天的內容如果都能理解並熟悉,我相信在2020年你絕對能夠用React找到一份工作。

因為2020年只剩2個月了,讀者練完這系列、投完履歷,2個月應該也過了......

後記

今年我沒有先準備。而且在參賽的期間,我同時要實習、準備推甄、準備另一個比賽、準備通識報告......可以完賽我真的覺得不可思議。

在撰寫技術教學文的時候,我會希望在介紹一個新單元時,應該要從「為什麼需要這個工具」開始,慢慢從「為什麼架構要這樣設計」,再繼續去提他的語法跟用法。雖然自己不是讀者很難判斷是不是這樣,但希望這次我有做到這個目標。

如果這中間有哪篇不清楚、有寫錯的地方,或是你看完這系列之後有什麼想跟我交流的,都歡迎留言在底下跟我說。


上一篇
【Day.29】React進階 - 以Redux Thunk處理非同步資料流
系列文
從比入門再往前一點開始,一直到深入React.js30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
harry xie
iT邦研究生 1 級 ‧ 2021-05-25 20:58:02

謝謝你的鐵人賽系列文,學到很多/images/emoticon/emoticon12.gif

Andy Chang iT邦研究生 3 級 ‧ 2021-05-26 12:52:41 檢舉

謝謝你~ 如果有不好理解的地方歡迎留言詢問!

我要留言

立即登入留言